/**************************************************************************
* This file is part of Hawkengine.
*
* Hawkengine is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Hawkengine is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Hawkengine.  If not, see <http://www.gnu.org/licenses/>.
**************************************************************************/


/**************************************************************************
* Dependencies
**************************************************************************/
#include "engine/common.h"
#include "engine/thread.h"
#include <stdarg.h>
#include <time.h>

/**************************************************************************
* Preprocessing
**************************************************************************/
#define LOG_FILE_TAIL "_log.txt"

/**************************************************************************
* Types
**************************************************************************/

/**************************************************************************
* Variables
**************************************************************************/
const char* APP_COPYRIGHT = "";

#ifdef USE_BACKWARDS_PATH_SEPARATOR
const char* PATH_SEPARATOR = "\\";
#else
const char* PATH_SEPARATOR = "/";
#endif

const float UPDATE_SECS = (1 / 30.0);
UCHAR debugging_level = 0;
log_level_type level_logged = LOG_LEVEL_WARNING;
BOOL IS_LITTLE_ENDIAN = TRUE;

const camera_type DEFAULT_CAMERA = {
  {0, 0, 0}, // pos
  {0, 1, 0}, // focus
  {0, 0, 1}, // up
};

/**************************************************************************
* Static Variables
**************************************************************************/
static thread_rwlock_type log_rwlock;
static FILE* log_file = NULL;
static const char* LOG_LEVEL_STRING[LOG_LEVEL_COUNT] = {
  "CRITICAL",
  "ERROR   ",
  "WARNING ",
  "INFO    ",
  "DEBUG   ",
};
static char data_dir[MAX_PATH_LENGTH] = {'.', 0};
static char resource_dir[MAX_PATH_LENGTH] = {'.', 0};

/**************************************************************************
* Function Prototypes
**************************************************************************/
static BOOL is_little_endian(void);

/**************************************************************************
* Function Implementations
**************************************************************************/
void common_init(void)
{
  thread_create_rwlock(&log_rwlock);

  IS_LITTLE_ENDIAN = is_little_endian();
}

void common_uninit(void)
{
  if (log_file) {
    fclose(log_file);
    log_file = NULL;
  }
}

#ifndef TIME_CUSTOM
void current_date(char* buffer)
{
  static const char* SCORE_DATE_FORMAT = "%d.%b.%Y";
 
  format_time(current_time_seconds(), SCORE_DATE_FORMAT, buffer);
}

UINT current_time_seconds(void)
{
  return (UINT)(time(NULL));
}

void format_time(UINT time, const char* format, char* buffer)
{
  struct tm* timeinfo;
  time_t rawtime;

  rawtime = (time_t)time;
  timeinfo = localtime(&rawtime);

  strftime(buffer, MAX_DATE_LENGTH, format, timeinfo);
}
#endif

#if !defined(TIME_CUSTOM) && !defined(TIMER_CUSTOM)
float elapsed_seconds(void)
{
  clock_t current_clock;
 
  current_clock = clock();
  return (float)(current_clock / (float)CLOCKS_PER_SEC);
}
#endif

void get_data_dir(char* dir)
{
  strcpy(dir, data_dir);
}

void get_resource_dir(char* dir)
{
  strcpy(dir, resource_dir);
}

static BOOL is_little_endian(void)
{
  union
  {
    long l;
    char c[sizeof(long)];
  } u;

  u.l = 1;
  if (u.c[0] == 1) {
    return TRUE;
  } else if (u.c[sizeof(long) - 1] == 1) {
    return FALSE;
  } else {
    return TRUE;
  }
}

void log_message(log_level_type log_level, const char* message, ...)
{
  static const char* DATE_TIME_FORMAT = "[%Y.%m.%d|%H:%M:%S]";
  static char date_time[128];
  static BOOL log_creation_attempted = FALSE;
  va_list args;

  if (log_level > level_logged) {
    return;
  }

  if (!log_file) {
    if (log_creation_attempted) {
      return;
    } else {
      // haven't tried to create the file, but need to.  go for it.
      char log_file_path[MAX_PATH_LENGTH];
      char log_file_name[256];
      int i;
 
      // set the file name
      strcpy(log_file_name, APP_TITLE);
      strcat(log_file_name, LOG_FILE_TAIL);
      for (i = 0; i < (int)strlen(log_file_name); i++) {
        log_file_name[i] = tolower(log_file_name[i]);
      }

      // set full path to log
      get_data_dir(log_file_path);
      strcat(log_file_path, PATH_SEPARATOR);
      strcat(log_file_path, log_file_name);

      log_file = fopen(log_file_path, "w+");

      log_creation_attempted = TRUE;

      if (!log_file) {
        return;
      }
    }
  }

  format_time(current_time_seconds(), DATE_TIME_FORMAT, date_time);

  thread_lock_write(&log_rwlock);
  fprintf(log_file, "%s %s ", LOG_LEVEL_STRING[log_level], date_time);
  va_start(args, message);
  vfprintf(log_file, message, args);
  va_end(args);
  fprintf(log_file, "\n");
  thread_unlock_write(&log_rwlock);
}

void log_read(char* buffer, int buffer_size, int start_idx)
{
  if (!log_file) {
    buffer[0] = 0;
    return;
  }

  thread_lock_read(&log_rwlock);

  // read from start_idx away from start of file
  fseek(log_file, start_idx, SEEK_SET);
  fread(buffer, 1, buffer_size, log_file);

  // move back to end of file
  fseek(log_file, 0, SEEK_END);

  thread_unlock_read(&log_rwlock);
}

int log_size(void)
{
  int size = 0;

  if (!log_file) {
    return 0;
  }

  thread_lock_read(&log_rwlock);

  // ensure at end of file then do a tell on the position
  fseek(log_file, 0, SEEK_END);
  size = ftell(log_file);

  thread_unlock_read(&log_rwlock);

  return size;
}

void set_data_dir(const char* dir)
{
  strcpy(data_dir, dir);
}

void set_resource_dir(const char* dir)
{
  strcpy(resource_dir, dir);
}
